package ar.edu.itba.it.poo.dd.backend
import ar.edu.itba.it.poo.dd.backend._
import scala.util.parsing.combinator.RegexParsers
import java.io.FileReader
import java.io.File


object TileType extends Enumeration {
   type TileType = Value
   val Player, Wall, StrengthBonus, HealthBonus, Dragon, Snake, Golem = Value
}

case class ParseException(message: String) extends Exception

case class Parser(val listener: BoardListener) extends RegexParsers {
  import TileType._
  override val whiteSpace = """[ \t]+|#.*""".r

  override protected def handleWhiteSpace(source: java.lang.CharSequence, offset: Int): Int = {
    var currentOffset = offset
    var newOffset = offset
    do {
      currentOffset = newOffset
      newOffset = super.handleWhiteSpace(source, currentOffset)
    } while (currentOffset != newOffset)
    newOffset
  }

  var playerToken: Option[Player] = None

  def NUMBER = "[0-9]+".r ^^ { num => num.toInt }
  def COMMA = ","
  def EOL = """\r?\n""".r

  def size: Parser[Tuple2[Int, Int]] = NUMBER ~ (COMMA ~> NUMBER) ^^ { case rows ~ columns => (rows, columns) }
  def name = "[a-zA-Z][ \ta-zA-Z0-9]+".r
  def dungeon = ((rep(EOL) ~> size <~ rep1(EOL)) ~ name <~ rep1(EOL)) ~ tiles <~ rep(EOL) ^^ {
    case (rows, columns) ~ name ~ tiles => {
      if (rows < 6 || columns < 6 || rows > 30 || columns > 30) throw new ParseException("Illegal board size")
      val cells: Array[Array[Tile]] = Array.tabulate(rows, columns) { (row, column) => new EmptyCell() }
      var player: Player = null
      
      for (t <- tiles) {
        t match {
          case (Wall, row: Int, column: Int) => cells(row)(column) = new Wall() 
          case (HealthBonus, row: Int, column: Int, ammount: Int) => cells(row)(column) = new HealthBonus(ammount)
          case (StrengthBonus, row: Int, column: Int, ammount: Int) => cells(row)(column) = new StrengthBonus(ammount)
          case (Golem, row: Int, column: Int, level: Int) => cells(row)(column) = Enemy.createGolem(level)
          case (Dragon, row: Int, column: Int, level: Int) => cells(row)(column) = Enemy.createDragon(level)
          case (Snake, row: Int, column: Int, level: Int) => cells(row)(column) = Enemy.createSnake(level)
          case (Player, row: Int, column: Int) => player = new Player(row, column, listener)
        }
      }
      new Board(name, cells, player, listener)
    }
  }
  def EOLrep = rep1(EOL)
  def tiles = repsep(tile, EOLrep)
  def tile = monster | bonus | wall | player
  def cell = NUMBER ~ (COMMA ~> NUMBER) ^^ { case row ~ column => (row, column) }
  def monsterType = "[123]".r ^^ { _.toInt }
  def monsterLevel = "[123]".r ^^ { _.toInt }
  def monster = (("3" ~ COMMA ~> cell <~ COMMA) ~ monsterType <~ COMMA) ~ monsterLevel <~ COMMA ~ "0" ^^ {
    case (row, column) ~ monsterType ~ monsterLevel => monsterType match {
      case 1 => (Golem, row, column, monsterLevel)
      case 2 => (Dragon, row, column, monsterLevel)
      case 3 => (Snake, row, column, monsterLevel)
    }
  }
  def bonusType = """[45]""".r ^^ { _.toInt }
  def bonus = ((bonusType <~ COMMA) ~ cell <~ COMMA ~ "0" ~ COMMA ~ "0" ~ COMMA) ~ NUMBER ^^ {
    case bonusType ~ cell ~ ammount => bonusType match {
      case 4 => (HealthBonus, cell._1, cell._2, ammount)
      case 5 => (StrengthBonus, cell._1, cell._2, ammount)
    }
  }
  def wall = "2" ~ COMMA ~> cell <~ COMMA ~ "0" ~ COMMA ~ "0" ~ COMMA ~ "0" ^^ { case (row, column) => (Wall, row, column) }
  def player = "1" ~ COMMA ~> cell <~ COMMA ~ "0" ~ COMMA ~ "0" ~ COMMA ~ "0" ^^ { case (row, column) => (Player, row, column) }
  def parse(file: File): Board = try {
    parseAll(dungeon, new FileReader(file)) match {
      case Success(result, _) => result
      case NoSuccess(msg, next) => throw new ParseException("Error parsing file. Failed at line %s, column %s.".format(next.pos.line, next.pos.column))
    }
  } catch {
      case e: Exception => throw new ParseException(e.getMessage)
  }
}